unit DIrr.Line3D;

{$I DIrr.inc}

interface

uses
  Math, DIrr.Math, DIrr.Vector3D;

type
  PLine3DF = ^TLine3DF;
  TLine3DF = record
    StartPos: TVector3DF;
    EndPos: TVector3DF;
  public
    constructor Create(xa, ya, za, xb, yb, zb: Single); overload;
    constructor Create(SP, EP: TVector3DF); overload;
  public
    class operator Add(Value1: TLine3DF; Value2: TVector3DF): TLine3DF;
    class operator Subtract(Value1: TLine3DF; Value2: TVector3DF): TLine3DF;

    class operator Equal(Value1, Value2: TLine3DF): Boolean;
    class operator NotEqual(Value1, Value2: TLine3DF): Boolean;

		//! Get length of line
		(* \return Length of line. *)
    function GetLength: Single;
		//! Get squared length of line
		(* \return Squared length of line. *)
    function GetQuaredLength: Single;
		//! Get middle of line
		(* \return Center of line. *)
    function GetMiddle: TVector3DF;
		//! Get vector of line
		(* \return vector of line. *)
    function GetVector: TVector3DF;
		//! Check if the given point is between start and end of the line.
		(** Assumes that the point is already somewhere on the line.
		\param point The point to test.
		\return True if point is on the line between start and end, else false.*)
    function IsPointBetweenStartAndEnd(const Point: TVector3DF): Boolean;
		//! Get the closest point on this line to a point
		(* \param point The point to compare to.
		\return The nearest point which is part of the line. *)
    function GetClosestPoint(const Point: TVector3DF): TVector3DF;
		//! Check if the line intersects with a shpere
		(* \param sorigin: Origin of the shpere.
		\param sradius: Radius of the sphere.
		\param outdistance: The distance to the first intersection point.
		\return True if there is an intersection.
		If there is one, the distance to the first intersection point
		is stored in outdistance. *)
    function GetIntersectionWithSphere(const Sorigin: TVector3DF;
      SRadius: Single; var OutDistance: Double): Boolean;
  end;

implementation

{ TLine3DF }

class operator TLine3DF.Add(Value1: TLine3DF;
  Value2: TVector3DF): TLine3DF;
begin
  Result.StartPos := Value1.StartPos + Value2;
  Result.EndPos := Value1.EndPos + Value2;
end;

constructor TLine3DF.Create(xa, ya, za, xb, yb, zb: Single);
begin
  StartPos.X := xa;
  StartPos.Y := ya;
  StartPos.Z := za;
  EndPos.X := xb;
  EndPos.Y := yb;
  EndPos.Z := zb;
end;

constructor TLine3DF.Create(SP, EP: TVector3DF);
begin
  StartPos := SP;
  EndPos := EP;
end;

class operator TLine3DF.Equal(Value1, Value2: TLine3DF): Boolean;
begin
  Result := ((Value1.StartPos = Value2.StartPos) and (Value1.EndPos = Value2.EndPos)) or
    ((Value1.StartPos = Value2.EndPos) and (Value1.EndPos = Value2.StartPos));
end;

function TLine3DF.GetClosestPoint(const Point: TVector3DF): TVector3DF;
var
  c, v: TVector3DF;
  d, t: Single;
begin
  c := Point - StartPos;
  v := EndPos - Point;

  d := v.GetLength;
  v := v / d;

  t := v.DotProduct(c);

  if t < 0.0 then
  begin
    Result := StartPos;
    Exit;
  end;

  if t > d then
  begin
    Result := EndPos;
    Exit;
  end;

  v := v * t;
  Result := StartPos + v;
end;

function TLine3DF.GetIntersectionWithSphere(const Sorigin: TVector3DF;
  SRadius: Single; var OutDistance: Double): Boolean;
var
  q, tmpv: TVector3DF;
  c, v, d: Single;
begin
  q := Sorigin - StartPos;
  c := q.GetLength;

  tmpv := GetVector;
  tmpv.Normalize;
  v := q.DotProduct(tmpv);
  d := SRadius * SRadius - (c * c - v * v);

  if (d < 0.0) then
  begin
    Result := False;
    Exit;
  end;

  OutDistance := v - Sqrt(d);
  Result := True;
end;

function TLine3DF.GetLength: Single;
begin
  Result := StartPos.GetDistanceFrom(EndPos);
end;

function TLine3DF.GetMiddle: TVector3DF;
begin
  Result := (StartPos + EndPos) * 0.5
end;

function TLine3DF.GetQuaredLength: Single;
begin
  Result := StartPos.GetDistanceFromSquared(EndPos);
end;

function TLine3DF.GetVector: TVector3DF;
begin
  Result := EndPos - StartPos;
end;

function TLine3DF.IsPointBetweenStartAndEnd(
  const Point: TVector3DF): Boolean;
begin
  Result := Point.IsBetweenPoints(StartPos, EndPos);
end;

class operator TLine3DF.NotEqual(Value1, Value2: TLine3DF): Boolean;
begin
  Result := not (((Value1.StartPos = Value2.StartPos) and (Value1.EndPos = Value2.EndPos)) or
    ((Value1.StartPos = Value2.EndPos) and (Value1.EndPos = Value2.StartPos)));
end;

class operator TLine3DF.Subtract(Value1: TLine3DF;
  Value2: TVector3DF): TLine3DF;
begin
  Result.StartPos := Value1.StartPos - Value2;
  Result.EndPos := Value1.EndPos - Value2;
end;

end.